package com.liujun.io.nio.trans.server;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import com.liujun.io.nio.trans.bean.ChannelAttachMsg;
import com.liujun.io.nio.trans.bean.FileMemonyChannel;
import com.liujun.io.nio.trans.conn.TransConnectManager;
import com.liujun.io.nio.trans.console.Config;
import com.liujun.io.nio.trans.console.ParamMsg;
import com.liujun.io.nio.trans.util.PropertiesUtils;

/**
 * 多路选择器的实现，仅针对连接
* 源文件名：MultiConnectionervice.java
* 文件版本：1.0.0
* 创建作者：liujun
* 创建日期：2016年8月24日
* 修改作者：liujun
* 修改日期：2016年8月24日
* 文件描述：liujun
* 版权所有：Copyright 2016 zjhz, Inc. All Rights Reserved.
*/
public class MultiConnectionervice implements Runnable {

    /**
     * 连接的多路选择器
     */
    private Selector connSelector;

    /**
     * 数据处理的多路选择器
     */
    private Selector processSelect;

    /**
     * 多路通道
     */
    ServerSocketChannel serverchannel;

    /**
     * 用来控制整个服务器的开始操作,true,表示开启，false 关闭
     */
    private AtomicBoolean stop = new AtomicBoolean(false);

    /**
     * 做连接管理信息
     * 
     * @字段说明 transConn
     */
    private TransConnectManager transConn;

    /**
     * 服务器端的ip
     * 
     * @字段说明 serverHost
     */
    private String serverMysqlHost = PropertiesUtils.getInstance().getValue(ParamMsg.PROPERTIS_SERVER_IP.getKey());

    /**
     * 服务器端的ip
     * 
     * @字段说明 serverHost
     */
    private int serverMysqlPort = Integer
            .parseInt(PropertiesUtils.getInstance().getValue(ParamMsg.PROPERTIS_SERVER_PORT.getKey()));

    /**
    * 构造方法
    * @param connSelector 连接选择器
    * @param transConn 通道管理器
    * @param processSelect 处理选择器
    */
    public MultiConnectionervice(Selector connSelector, TransConnectManager transConn, Selector processSelect) {
        this.connSelector = connSelector;
        this.transConn = transConn;
        this.processSelect = processSelect;
    }

    public void stop() {
        this.stop.set(true);
    }

    @Override
    public void run() {
        // 如果当前开始不是关闭的才能运行
        while (!stop.get()) {
            try {
                // 在此通道阻塞1000毫秒
                int readyChannels = connSelector.select(100);

                if (readyChannels == 0) {
                    continue;
                }

                // 轮循进行检查是否有已经准备就绪的key
                Set<SelectionKey> selectkey = connSelector.selectedKeys();

                Iterator<SelectionKey> iter = selectkey.iterator();

                while (iter.hasNext()) {
                    SelectionKey sekey = iter.next();
                    iter.remove();
                    // 将当前的数据交给对应的方法来处理
                    try {
                        this.handleInput(sekey);
                    } catch (Exception e) {
                        e.printStackTrace();
                        if (null != sekey) {
                            sekey.cancel();
                            if (sekey.channel() != null) {
                                sekey.channel().close();
                            }
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 进行通道的处理操作 方法描述
     * 
     * @param key
     * @throws IOException
     * @创建日期 2016年8月24日
     */
    private void handleInput(SelectionKey key) throws IOException {
        // 如果当前通道是有效的
        if (key.isValid()) {
            // 如果当前的连接已经用于套接字接受操作
            if (key.isAcceptable()) {
                // 得到当前注册的通道
                ServerSocketChannel ssc = (ServerSocketChannel) key.channel();

                ChannelAttachMsg type = new ChannelAttachMsg(Config.CONFIG_TYPE_FIRST.getKey());
                SocketChannel sc = ssc.accept();
                // 设置为非阻塞
                sc.configureBlocking(false);

                // 生成通道信息
                int id = transConn.regConnChannel();

                // 将本地代理服务端的信息附着到通道中
                FileMemonyChannel fileChannel = transConn.getFileChannel(id);
                type.setId(id);
                type.setTransChannel(fileChannel);
                sc.register(processSelect, SelectionKey.OP_READ | SelectionKey.OP_WRITE, type);

                // 将本地代理连接mysql的信息附着到连接mysql的通道中
                ChannelAttachMsg attachMysql = new ChannelAttachMsg(Config.CONFIG_TYPE_END.getKey());
                attachMysql.setId(id);
                attachMysql.setTransChannel(fileChannel);
                this.openNewSocketChann(serverMysqlHost, serverMysqlPort, connSelector, attachMysql);

            }

            // 如果为连接信息
            if (key.isConnectable()) {
                SocketChannel sc = (SocketChannel) key.channel();
                // 如果已经完成连接
                if (sc.finishConnect()) {
                    ChannelAttachMsg attach = (ChannelAttachMsg) key.attachment();
                    sc.register(processSelect, SelectionKey.OP_READ | SelectionKey.OP_WRITE, attach);
                }
            }
        }
        // 如果此键已经失效，则关闭连接
        else {
            key.cancel();
            key.channel().close();
        }
    }

    /**
     * 创建连接信息 方法描述
     * 
     * @param host
     * @param port
     * @param selectorIn
     * @创建日期 2016年8月24日
     */
    public void openNewSocketChann(String host, int port, Selector selectorIn, Object attach) {
        try {
            // 打开客户端流通道
            SocketChannel sc = SocketChannel.open();
            // 设置为非阻塞模式
            sc.configureBlocking(false);

            // 如果服用服务器的连接已经连接成功，需要将读取消息注册到多路复用器上，然后发送消息
            if (sc.connect(new InetSocketAddress(host, port))) {
                // 注册读取操作到多路复用器上
                sc.register(processSelect, SelectionKey.OP_READ | SelectionKey.OP_WRITE, attach);
            } else {
                // 注册连接
                sc.register(selectorIn, SelectionKey.OP_CONNECT, attach);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
